# 函数也是一个对象, 所以也可以作为参数传递
# def 函数名([arg..]):
#       代码块
# 函数：直接通过 函数名() 调用，参数传几个就是几个
# 方法：通过 对象.方法名() 调用，对象内的方法默认会比函数多一个参数，默认会传递类对象本身作为第一个参数(相当于java中this)

def fn(s='hello world'): 
    print(s)

# fn是函数对象实体，fn()是调用函数
fn()
print(fn, type(fn))

# 带参函数，c=1为默认参数，可以不传递
def sum_3(a, b, c=1):
    print('a=',a,'b=',b,'c=',c)
    return a + b + c # 返回值

print(sum_3(1,2), sum_3(2,3,4))
# 默认传参按照参数定义位置进行匹配实参，可以通过参数名指定参数，称为关键字参数, 使用关键字参数不需要关系参数顺序
print(sum_3(c=10,a=1,b=1), sum_3(1, c=2, b = 3)) # 位置参数和关键字参数可以混合使用但是位置参数必须放在前面！

# 回调的一种形式
def fn1(p, callback) :
    p('hello')
    callback('callback value')
# 将print作为参数传递给函数
fn1(print, fn)

# 不定长参数，在最后一个参数名上添加*，可以接收一个元组，
# a,b,*c = (1,2,3,4) 元组的解包在函数参数上一样适用, 参数相当于封包
# 带*的参数可以放在任意位置，但是*号参数后面的参数在传递时必须使用关键字参数

# 所有参数都给nums
def fn2(*nums) :
    return sum(nums)

# 第一个位置参数给a, 剩下的给b， 通过关键字参数c=x指定的给c
def fn3(a, *b, c):
    t = list(b)
    t.append(a)
    t.append(c)
    return sum(t)
# 前两个位置参数给a, b. 剩下的所有参数给c
def fn4(a, b, *c) :
    t = list(c)
    t.append(a)
    t.append(b)
    return sum(t)
# 第一个参数为*的标识， 标识该函数所有的参数都必须使用关键字参数指定
def fn5(*, a, b):
    return a + b

# **参数名，该参数接收所有未定义的参数，字典类型，存储{参数名:参数值, 参数名:参数值...}
# **参数只能有一个，并且只能放到最后一个位置
def fn6(**a): 
    print(a, type(a))

n = [1,2,3] # 可以是list、tuple 
# 将序列变量前增加*，将自动解包， *变量名为对序列解包
# 序列的元素个数必须和函数参数个数相同
print(sum_3(*n))
# 通过**变量名可以对字典进行解包
# 字典的key需要和函数参数名对应
d = {'a':1,'b':2,'c':3}
print(sum_3(**d))

print(fn2(1,2,3,4,5,6), fn3(1, 2,4,5,c=6), fn4(1,2,3,4,5), fn5(a=1,b=2), fn6(a=1,b=2))

# return 返回值，可以返回任意对象，如果不跟返回值，则直接返回不再执行下面的代码
def fn7() :
    # return返回可以返回任意数据类型对象，包括函数
    return 100






# help() 帮助函数，可以查看默写函数的用法。
help(print) # 查看print的用法
help(sum) # 查看sum的用法

# 自定义文档说明, 参数描述，参数名:参数类型，注意此处参数类型只是提示使用者要传入的参数类型，但是并不是强制，也就是说传别的类型也不会有问题
def fn8(a:int, b:bool, c:str='hello') -> int: # -> int代表返回值为int, 并且最后一个默认值为hello
    # 文档编写在函数内部使用多行字符串标注
    '''
        my first doc
            a : int value
            b : bool value
            c : str value
    '''
    print('a=', a, 'b=', b, 'c=',c)
    return 0

fn8(123, True)
# 查看fn8函数的帮助
help(fn8)





# 变量作用域scope，与java相同，不过如果在函数中修改全局变量需要使用global生命
a = 2
def fn9() :
    # 声明使用全局变量，并更改其值, 不声明，则为局部变量a
    global a
    a = 1
    print(a)

fn9()
print(a)

# 命名空间 namespace, 命名空间是变量存储位置，每一个作用域都有一个命名空间
# 命名空间就是一个用于存储某个作用域的字典，全局、局部作用域

# locals() 获取本地命名空间，此处为全局命名空间，如果在函数内则为函数内的命名空间。 返回的是字典
scope = locals()
print(scope, type(scope))
print(scope['a']) # 相当于直接访问a这个全局变量

scope['aa'] = 'bb' # 相当于定义一个全局变量
print(aa) # 访问全局变量

def fn10():
    abc = 'hello'
    # 函数内部作用域
    scope = locals()
    # globals() 全局命名空间，可以在任意位置获取全局命名空间
    scope_globals = globals()
    print(scope, type(scope), scope_globals)

fn10()





# 递归 阶乘
def factorial(n:int) -> int:
    '''
        factorial for numbers
            n: int the number of factorial
    '''
    if n == 1:
        return n
    else :
        return n * factorial(n - 1)

print('factorial ->', factorial(5))

# n ** m 次方递归
def power(n:int, m:int) -> int:
    '''
        n power m
            n : the number of power
            m : power value
    '''
    if m == 1:
        return n
    else :
        return n * power(n, m - 1)

print('power ->', power(2, 4))

# 回文检测：abcba 左右对称的被称为回文
t = 'abcba'
def mirror(s:str) -> bool:
    '''
        use piece option of array
    '''
    if len(s) < 2 :
        return True
    elif s[0] != s[-1] : # 首字符与倒数第一个字符比较
        return False
    else :
        # 传递切片后的数组，从第1个字符到倒数第一个字符，不包含倒数第一个字符
        return mirror(s[1:-1]) 

print('mirror test ->', mirror(t))        




# 高阶函数： 至少有一个参数接收函数作为参数，或者返回函数对象作为返回值
# 1. 用函数对象作为参数
# 2. 返回值为函数对象
# 满足以上两个条件的任意一个条件，就可以称为高阶函数
def _f_even(n):
    return n % 2 == 0

def _f_odd(n):
    return n % 2 != 0

# 使用函数对象作为参数的高阶函数，根据函数逻辑筛选数据
def fn11(lst:list, _fn) -> list:
    '''
        filter list by _fn function args
            lst: list data
            _fn: function for filter option
    '''
    ret = []
    for n in lst:
        if _fn(n):
            ret.append(n)
    return ret
# 根据传递的函数参数不同获取列表的奇偶数
ar = [1,2,3,4,5,6,7]
print(fn11(ar, _f_odd))
print(fn11(ar, _f_even))
help(fn11)

# filter(function ,list) python内置函数，过滤iterable数据
print(list(filter(_f_even, ar))) # filter()返回的是iterable对象，需要通过list转换否则打印出来的是对象地址描述

# 匿名函数 lambda表达式
# 语法 lambda 参数列表: 表达式
print((lambda a,b : a + b)(1,2)) # lambda表达式代表一个匿名函数，通过匿名函数(参数1，参数2)可直接使用
lbd = lambda a,b : a + b # lambda表达式不支持多行
print(lbd(1,2)) # 相当于上一个的操作
print(list(filter(lambda n : n % 2 == 0, ar))) # 第一个参数lambda表达式的匿名函数
# map(function ,iterable) 相当于java中的map操作，迭代并处理
print(list(map(lambda n: n+1, ar))) # 将列表中的每个数+1并返回iterable对象

# sort(key)方法也可以传递函数参数，key为关键字参数，代表一个函数，该参数对应的函数需要接收一个值并返回一个值作为列表内比较 
ar.sort(key=lambda a: a*-1) # 将每个值取反进行比较
print(ar)
# sorted()函数，不是list的一个方法，是一个函数，通过sorted函数操作的列表不会修改原列表，会产生一个新的列表
print(sorted(ar, key=lambda a: a* -1))



# 使用函数对象作为返回值，也是高阶函数的一种
def fn12() :
    def fn12_inner():
        print('inner function')
    return fn12_inner

f = fn12()
# 返回的是函数对象，是fn12的内部函数，获取到函数对象后可以直接调用
print(f)
f() # 调用fn12返回的函数
# 函数内部定义的函数通常外部作用域无法访问，当作为返回值返回时，外部可以使用内部函数
# 内部函数作为返回值通常应用场景为：内部函数需要访问其父函数中定义的变量但是该变量又不能对外可见，该形式又称为闭包 
def fn13():
    # 函数局部变量，外部不可见，但是内部函数可见
    nums = []
    # 计算数字叠加求和
    def fn13_inner(n):
        nums.append(n)
        return sum(nums)
    return fn13_inner

f = fn13()
print(f(1), f(2), f(3)) # 此时传递过的数会依次叠加求和，但是nums外部不可见，只有内部函数可见
# 闭包形成的3个条件：
#   1. 函数存在嵌套的内部函数
#   2. 函数使用内部函数作为返回值(不返回，外部无法使用内部函数)
#   3. 内部函数使用其父函数中定义的变量

# 装饰器 不修改原函数的情况下，增加业务处理，类似于spring-aop， 满足开闭原则
# 装饰器函数使用方式：
#       1. 通过调用装饰器函数，传递原函数，使用返回的函数对象
#       2. 通过@装饰器函数名称，注解在需要被装饰的函数上，直接调用原函数
#          同一个函数上可以有多个装饰器，执行先后顺序为从最下面的注解依次往上包装执行
# 在该函数前后打印begin end 
def fn14():
    print('hello')
# 定义装饰器函数
def decorate_fn(_fn):
    '''
        装饰器定义内部函数，并接受任意参数，将内部函数作为返回值返回
        参数：
            _fn : 被装饰的函数对象
    '''
    # 此处内部函数的参数，*args代表任意位置参数封包为元组，**keyword_args代表任意关键字参数封包为字典
    def _decorate_inner(*args, **keyword_args): # 参数封包
        print('begin function', _fn)
        # 此处的函数调用传递参数，*args代表args参数解包为位置参数，**keyword_args代表解包为关键字参数
        result = _fn(*args, **keyword_args) # 参数解包， 如有返回值将返回值接收并返回
        print('end function', _fn)
        return result # 返回函数调用结果
    return _decorate_inner
# 调用装饰器函数将原函数装饰
f = decorate_fn(fn14)
print(f()) # 返回的为装饰函数的内部函数，因为fn14没有参数所以不需要传递参数
print('+'*66)
f = decorate_fn(lambda a,b: a+b) # 包装匿名函数 两个数相加
print(f(1,2)) # 传递参数调用函数

print(f) # f为返回的函数对象

# 使用注解形式装饰器，直接装饰原方法
@decorate_fn # 第二层，对第一层进行装饰，第一层包装后是decorate_fn的inner内部函数，该注解是对inner进行再装饰
@decorate_fn # 第一层，对fn_hello进行装饰，先执行该层的包装，再执行第二层的
def fn_hello():
    print('hello')
# 直接调用原方法即可
fn_hello()

